# -*- coding: utf-8 -*-
import allure
import pytest
from selenium import webdriver
from selenium.webdriver.chrome.options import Options
from selenium.webdriver.chrome.service import Service
from selenium.webdriver.common.keys import Keys
from selenium.webdriver.common.by import By
from time import sleep
from selenium.webdriver.support.select import Select
from selenium.webdriver.common.action_chains import ActionChains


class WebApi:
    _instance = None

    def __new__(cls):
        if cls._instance is None:
            # 创建新的实例
            instance = super().__new__(cls)
            cls._instance = instance
        return cls._instance

    def __init__(self):
        # 判断实例是否已经初始化
        if not hasattr(self, 'authorization_time'):
            # print("WebApi::__init__")
            self.driver = None
            self.chrome_options = Options()
            # 禁用沙盒模式（增加兼容性）
            self.chrome_options.add_argument('--no-sandbox')
            # 添加忽略 SSL 证书错误的参数
            self.chrome_options.add_argument('--ignore-certificate-errors')
            self.chrome_options.add_argument('--allow-running-insecure-content')
            self.chrome_options.add_argument('--enable-unsafe-swiftshader')
            # 添加禁用 GPU 加速的参数
            self.chrome_options.add_argument('--disable-gpu')
            # 保持游览器打开状态（默认是代码执行完毕后自动关闭）
            self.chrome_options.add_experimental_option('detach', True)

    # 获取游览器名称
    def get_driver_name(self):
        return self.driver.name

    # 获取游览器版本
    def get_driver_version(self):
        return self.driver.capabilities['version']

    # 强制等待
    def web_sleep(self, args):
        try:
            ms = int(args[0])
            sleep(ms / 1000.0)
        except Exception as e:
            print(f"用例执行失败，强制等待参数错误<{args[0]}>，异常信息：{str(e)}")
            pytest.fail(f"用例执行失败，强制等待参数错误<{args[0]}>，异常信息：{str(e)}")

    # 打开浏览器
    def web_open(self, driver, driver_path="lib/Chrome/chromedriver.exe"):
        try:

            # 首字母大写
            driver = driver.capitalize()
            if driver == "Chrome":
                service = Service(driver_path)
                self.driver = getattr(webdriver, driver)(service=service, options=self.chrome_options)
            else:
                # self.driver = getattr(webdriver, driver)(service=service)
                print(f"当前游览器<{driver}>未做适配")
            # 隐式等待
            self.driver.implicitly_wait(10)
        except Exception as e:
            print(f"用例执行失败，浏览器<{driver}>，驱动路径<{driver_path}>，异常信息：{str(e)}")
            pytest.fail(f"用例执行失败，浏览器<{driver}>，驱动路径<{driver_path}>，异常信息：{str(e)}")

    # 关闭浏览器
    def web_quit(self):
        self.driver.quit()

    # 关闭浏览器第一个窗口
    def web_close(self):
        self.driver.close()

    # 访问网址
    def web_url(self, args):
        url = args[0]
        self.driver.get(url)

    # 刷新页面
    def web_refresh(self, args):
        self.driver.refresh()

    # 前进页面
    def web_forward(self, args):
        self.driver.forward()

    # 返回页面
    def web_back(self, args):
        self.driver.back()

    """
        id定位        by_id                 id="kw"
        name定位      by_name               name="wd"
        class定位     by_class_name         class="s_ipt"
        tag定位       by_tag_name           "input"
        link定位      by_link_text          "新闻"
        partial定位   by_partial_link_text  "新"
        xpath定位     by_xpath              "//*[@id='kw']"
        css定位       by_css_selector       "#kw"     "div input"     "div>input"     "p+p"       ".box~p"      ".s_ipt"
        
    """
    # 定位元素
    def find_element(self, args):
        initial_locator = args[0]
        if initial_locator.startswith("//*") or initial_locator.startswith("/html"):
            ele = "XPATH"
        else:
            initial_locator_split = initial_locator.split("=", 1)
            ele = initial_locator_split[0].upper()
            initial_locator = initial_locator_split[1]
            if (initial_locator.startswith("'") and initial_locator.endswith("'"))\
                    or (initial_locator.startswith("\"") and initial_locator.endswith("\"")):
                initial_locator = initial_locator[1:-1]
            if ele == "CLASS":
                ele = "CLASS_NAME"
            elif ele == "CSS":
                ele = "CSS_SELECTOR"
        element = self.driver.find_element(getattr(By, ele), initial_locator)
        return element

    # # get_attribute获取属性     id      name        src     class
    # def web_get_attribute(self, loc):
    #     self.find_element(loc).get_attribute(loc[1])
    # 输入数据
    def web_input(self, args):
        text = args[1]
        find_element = self.find_element(args)
        find_element.send_keys(text)
        return find_element

    """
        send_keys(Keys.BACK_SPACE)：删除键(BackSpace)
        send_keys(Keys.SPACE)：空格键(Space)
        send_keys(Keys.TAB)：制表键(TAB)
        send_keys(Keys.ESCAPE)：回退键(ESCAPE)
        send_keys(Keys.ENTER)：回车键(ENTER)
        send_keys(Keys.CONTRL,'a')：全选(Ctrl+A)
        send_keys(Keys.CONTRL,'c')：复制(Ctrl+C)
        send_keys(Keys.CONTRL,'x')：剪切(Ctrl+X)
        send_keys(Keys.CONTRL,'v')：粘贴(Ctrl+V)
        send_keys(Keys.F1)：键盘F1
        .....
        send_keys(Keys.F12)：键盘F12
    """
    # 键盘操作
    def web_send_keys(self, args):
        find_element = self.find_element(args)
        input_str = args[1]
        input_parts = []

        while input_str:
            if input_str.startswith("Keys."):
                end_index = input_str.find(",") if "," in input_str else len(input_str)
                input_key_str = input_str[:end_index].strip()

                # 处理重复次数和组合键
                if "*" in input_key_str:
                    key_part, repeat_part = input_key_str.split("*", 1)
                    try:
                        key_name = key_part[5:].strip()
                        key = getattr(Keys, key_name)
                        repeat_count = int(repeat_part.strip())
                        input_parts.extend([key] * repeat_count)
                    except (AttributeError, ValueError) as e:
                        print(f"无效的输入: {input_key_str}, 错误信息: {e}")
                elif "+" in input_key_str:
                    keys_parts = input_key_str.split("+")
                    actions = ActionChains(self.driver)
                    for key_part in keys_parts:
                        try:
                            key_name = key_part[5:].strip()
                            key = getattr(Keys, key_name)
                            actions.key_down(key)
                        except AttributeError:
                            actions.send_keys(key_part.strip())
                    for key_part in reversed(keys_parts):
                        try:
                            key_name = key_part[5:].strip()
                            key = getattr(Keys, key_name)
                            actions.key_up(key)
                        except AttributeError:
                            pass
                    input_parts.append(actions)
                else:
                    try:
                        key_name = input_key_str[5:].strip()
                        key = getattr(Keys, key_name)
                        input_parts.append(key)
                    except AttributeError:
                        print(f"无效的按键名称: {input_key_str}")

                input_str = input_str[end_index + 1:].strip() if end_index < len(input_str) else ""
            else:
                comma_index = input_str.find("Keys.")
                if comma_index == -1:
                    input_parts.append(input_str)
                    input_str = ""
                else:
                    sub_str = input_str[:comma_index].strip()
                    # 去除最后一个逗号
                    if sub_str.endswith(','):
                        sub_str = sub_str.rstrip(',')
                    input_parts.append(sub_str)
                    input_str = input_str[comma_index:].strip()
        # 分别处理普通按键和 ActionChains 对象
        for part in input_parts:
            if isinstance(part, ActionChains):
                # # 确保焦点在目标元素上
                # self.driver.execute_script("arguments[0].focus();", find_element)
                part.perform()
            else:
                find_element.send_keys(part)

    # 输入文本并回车确认
    def web_submit(self, args):
        find_element = self.find_element(args)
        find_element.submit()

    # 清除数据
    def web_clear(self, args):
        self.find_element(args).clear()

    # 点击
    def web_click(self, args):
        self.find_element(args).click()

    # 鼠标右击
    def web_right_click(self, args):
        ActionChains(self.driver).context_click(self.find_element(args)).perform()

    # 鼠标双击
    def web_double_click(self, args):
        ActionChains(self.driver).double_click(self.find_element(args)).perform()

    # 定位悬停
    def web_move_to_element(self, args):
        move = self.find_element(args)
        ActionChains(self.driver).move_to_element(move).perform()

    # 下拉框
    """
        根据索引选择：     Select(element).select_by_index("0")
        根据value值选择： Select(element).select_by_value("选项一")
        根据文本值选择：   Select(element).select_by_visible_text("选项一")
    """
    # 选中
    def web_select(self, args):
        args_1 = args[1]
        if args_1.isdigit():
            Select(self.find_element(args)).select_by_index(int(args_1))      # 输入数值
        elif args_1.startswith("value="):
            Select(self.find_element(args)).select_by_value(args_1[6:])       # "value=选项一"
        else:
            Select(self.find_element(args)).select_by_visible_text(args_1)    # 文本

    # 取消选中项
    def web_deselect(self, args):
        args_1 = args[1]
        if args_1 == "all":
            Select(self.find_element(args)).deselect_all()                     # all
        elif args_1.isdigit():
            Select(self.find_element(args)).deselect_by_index(int(args_1))     # 输入数值
        elif args_1.startswith("value="):
            Select(self.find_element(args)).deselect_by_value(args_1[6:])      # "value=选项一"
        else:
            Select(self.find_element(args)).deselect_by_visible_text(args_1)   # 文本

    # 选项卡切换
    # 新建一个选项卡
    def web_execute_script(self, args):
        self.driver.execute_script("window.open()")

    # 切换到对应的窗口
    def web_switch_to_window(self, args):
        try:
            args_0 = int(args[0])
            if 0 <= args_0 < len(self.driver.window_handles):
                self.driver.switch_to.window(self.driver.window_handles[args_0])
            else:
                print(f"传入的索引 {args_0} 超出了有效范围。")
                pytest.fail(f"传入的索引 {args_0} 超出了有效范围。")
        except ValueError:
            print(f"传入的参数 {args[0]} 不是有效的整数。")
            pytest.fail(f"传入的参数 {args[0]} 不是有效的整数。")

    # 断言
    # 网页标题：self.driver.title
    # 当前网址：self.driver.current_url
    # 浏览器名称：self.driver.name
    # 网页源码：self.driver.page_source
    # 获取当前窗口的句柄：self.driver.current_window_handle
    # 获取属性值：self.find_element(loc).get_attribute("src")     获取图片地址
    # 获取文本：self.find_element(loc).text
    # 获取id：self.find_element(loc).id
    # 获取位置：self.find_element(loc).location
    # 获取标签名：self.find_element(loc).tag_name
    # 获取大小：self.find_element(loc).size
    # 判断元素是否显示：self.find_element(loc).is_displayed()
    # 元素是否可点击：self.find_element(loc).is_enabled()
    # 元素是否被选中：self.find_element(loc).is_selected()

    def web_assert(self, args):
        try:
            # 统一属性名称大小写
            attr_name = args[0].lower()

            if attr_name in ["is_displayed", "is_enabled", "is_selected"]:
                actual_value = getattr(self.find_element(args[1:]), attr_name)()
                element_status = args[2].lower()
                if element_status == "true":
                    expected_value = True
                elif element_status == "false":
                    expected_value = False
                else:
                    expected_value = True
            elif attr_name in ["text", "id", "location", "tag_name", "size"]:
                actual_value = getattr(self.find_element(args[1:]), attr_name)
                expected_value = args[2]
            elif attr_name[:14] in ["get_attribute:", "get_attribute："]:
                attribute = attr_name[14:].strip()
                actual_value = self.find_element(args[1:]).get_attribute(attribute)
                expected_value = args[2]
            else:
                # 获取属性值
                actual_value = getattr(self.driver, attr_name)
                expected_value = args[1]
            # 格式化错误信息
            error_message = f"预期 {expected_value} 等于 {actual_value}，但实际不相等。"
            # 执行断言
            assert expected_value == actual_value, error_message
            allure.attach(f"断言成功: 预期 {expected_value} 等于 {actual_value}。", "断言信息", allure.attachment_type.TEXT)
        except AssertionError as e:
            # 添加Allure描述
            allure.attach(str(e), "断言失败信息", allure.attachment_type.TEXT)
            raise e
        except ValueError as e:
            allure.attach(f"参数错误: {e}", "断言失败信息", allure.attachment_type.TEXT)
            raise e
        except Exception as e:
            allure.attach(f"发生未知错误: {e}", "断言失败信息", allure.attachment_type.TEXT)
            raise e

    # 获取截图
    def web_screenshot(self, args):
        allure.attach(self.driver.get_screenshot_as_png(), args[0], allure.attachment_type.PNG)


web_api = WebApi()
